﻿using System;
using System.Collections.Generic;
using System.Linq;
using TfsMerge.Interface;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Reflection;
using System.Globalization;
using System.Windows.Forms;
using TfsMerge.View;
using Microsoft.TeamFoundation.VersionControl.Controls;

namespace TfsMerge.Service
{
    internal class Tfs : ITfs
    {
        readonly string _projectName;
        TfsTeamProjectCollection _tfs;

        WorkItemStore WorkItemStore
        {
            get { return _tfs.GetService<WorkItemStore>(); }
        }

        VersionControlServer VersionControlServer
        {
            get { return _tfs.GetService<VersionControlServer>(); }
        }

        Workspace Workspace
        {
            get { return VersionControlServer.GetWorkspace(Environment.MachineName, VersionControlServer.AuthorizedUser); }
        }

        Project Project
        {
            get { return WorkItemStore.Projects[_projectName]; }
        }

        public Tfs(string tfsUrl, string projectName)
        {
            _projectName = projectName;
            _tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsUrl));
            _tfs.EnsureAuthenticated();

            Workstation.Current.EnsureUpdateWorkspaceInfoCache(VersionControlServer, VersionControlServer.AuthorizedUser);
        }

        public void Reconnect()
        {
            _tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(_tfs.Uri);
            _tfs.EnsureAuthenticated();
        }

        public WorkItem GetWorkItem(int id)
        {
            return WorkItemStore.GetWorkItem(id);
        }


        public int? CheckIn(string branch, string comment, int? associatedWorkItemId)
        {
            int? res = null;

            var pendingChanges = Workspace.GetPendingChanges(branch, RecursionType.Full);
            if (pendingChanges.Length > 0)
                res = Workspace.CheckIn(pendingChanges, VersionControlServer.AuthorizedUser, comment, null, GetCheckedInfos(associatedWorkItemId), null);

            return res;
        }

        private WorkItemCheckinInfo[] GetCheckedInfos(int? associatedWorkItemId)
        {
            WorkItemCheckinInfo[] checkedInfos = null;
            if (associatedWorkItemId.HasValue)
            {
                checkedInfos = new[] { new WorkItemCheckinInfo(GetWorkItem(associatedWorkItemId.Value), WorkItemCheckinAction.Associate) };
            }
            return checkedInfos;
        }

        public void Undo(string branch)
        {            
            var pendingChanges = Workspace.GetPendingChanges(branch, RecursionType.Full);
            if (pendingChanges.Length > 0)
                Workspace.Undo(pendingChanges);
        }


        public GetStatus Get(string[] items, VersionSpec version, RecursionType recursion, GetOptions options)
        {
            return Workspace.Get(items, version, recursion, options);
        }

        public Changeset GetChangeset(int changesetId)
        {
            return VersionControlServer.GetChangeset(changesetId);
        }

        public IEnumerable<Changeset> GetHistory(string branch, int size = int.MaxValue)
        {
            return VersionControlServer.QueryHistory(branch, VersionSpec.Latest, 0, RecursionType.Full, null, null,
                VersionSpec.Latest, int.MaxValue, true, false).Cast<Changeset>();
        }

        public MergeCandidate[] GetMergeCandidates(string sourceBranch, string targetBranch)
        {
            return VersionControlServer.GetMergeCandidates(sourceBranch, targetBranch, RecursionType.Full);
        }

        public GetStatus CheckMerge(string sourceBranch, string targetBranch, int changesetId)
        {
            return MergeInner(sourceBranch, targetBranch, changesetId, changesetId, MergeOptions.NoMerge);
        }

        public GetStatus Merge(string sourceBranch, string targetBranch, int changesetId)
        {
            return MergeInner(sourceBranch, targetBranch, changesetId, changesetId, MergeOptions.None);
        }

        public GetStatus Merge(string sourceBranch, string targetBranch, int changesetIdStart, int changesetIdEnd)
        {
            return MergeInner(sourceBranch, targetBranch, changesetIdStart, changesetIdEnd, MergeOptions.None);
        }

        private GetStatus MergeInner(string sourceBranch, string targetBranch, int changesetIdStart, int changesetIdEnd, MergeOptions mergeOptions)
        {
            return Workspace.Merge(sourceBranch, targetBranch,
                new ChangesetVersionSpec(changesetIdStart),
                new ChangesetVersionSpec(changesetIdEnd),
                LockLevel.None, RecursionType.Full, mergeOptions);
        }

        public Conflict[] GetConflicts(string branch)
        {
            return Workspace.QueryConflicts(new[] { branch }, true);
        }



        public IEnumerable<WorkItem> ExecQuery(QueryDefinition queryDef)
        {
            var query = new Query(WorkItemStore, queryDef.QueryText, new Dictionary<string, object> { { "project", _projectName } });

            if (queryDef.QueryType != QueryType.List)
                return ExecQueryTree(query);
                
            return query.RunQuery().Cast<WorkItem>();
        }

        private IEnumerable<WorkItem> ExecQueryTree(Query query)
        {
            var links = query.RunLinkQuery();
            return links.Select(link => WorkItemStore.GetWorkItem(link.TargetId));
        }


        public void ShowChangesetDetailsDialog(Form owner, int changesetId)
        {
            var args = new object[] { VersionControlServer, VersionControlServer.GetChangeset(changesetId), false };

            using (var dialog = CreateVersionControlDialog("Microsoft.TeamFoundation.VersionControl.Controls.DialogChangesetDetails", args))
            {
                dialog.StartPosition = FormStartPosition.CenterParent;
                dialog.ShowDialog(owner);
            }
        }

        public void ShowConflictsDialog(Form owner, Conflict[] conflicts)
        {
            var args = new object[] { Workspace, conflicts.Select(x => x.YourServerItemSource).ToArray(), true };

            using (var dialog = CreateVersionControlDialog("Microsoft.TeamFoundation.VersionControl.Controls.DialogResolveConflicts", args))
            {
                dialog.StartPosition = FormStartPosition.CenterParent;
                dialog.ShowDialog(owner);
            }
        }

        private Form CreateVersionControlDialog(string typeName, object[] args)
        {
            var assembly = Assembly.GetAssembly(typeof(WorkItemPolicy));
            const BindingFlags bindingFlags = BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;

            return assembly.CreateInstance(typeName, false, bindingFlags, null, args, CultureInfo.CurrentCulture, null) as Form;
        }

        public void ShowWorkItemDialog(Form owner, int id)
        {
            using (var dialog = new WorkItemView(GetWorkItem(id)))
            {
                dialog.StartPosition = FormStartPosition.CenterParent;
                dialog.ShowDialog(owner);
            }
        }

        public WorkItem ShowWorkItemSelectorDialog(Form owner)
        {
            WorkItem wi = null;
            using (var dialog = new WorkItemSelector(this, QueryHierarchyExpand(Project.QueryHierarchy)))
            {
                dialog.StartPosition = FormStartPosition.CenterParent;
                dialog.ShowDialog(owner);

                if (dialog.DialogResult == DialogResult.OK)
                    wi = dialog.WorkItem;
            }
            return wi;
        }

        private IEnumerable<QueryItem> QueryHierarchyExpand(IEnumerable<QueryItem> queryItems)
        {
            foreach (var itm in queryItems)
            {
                yield return itm;

                if (itm is IEnumerable<QueryItem>)
                {
                    foreach (var subItm in QueryHierarchyExpand(itm as IEnumerable<QueryItem>))
                        yield return subItm;
                }
            }
        }

        public void ShowTrackView(Form owner, int changesetId)
        {
            var merges = TrackMerges(changesetId).ToArray();
            if (!merges.Any())
                return;

            using (var dialog = new TrackView(changesetId, merges))
            {
                dialog.StartPosition = FormStartPosition.CenterParent;
                dialog.ShowDialog(owner);
            }
        }


        public IEnumerable<BranchObject> GetBranches()
        {
            return VersionControlServer.QueryRootBranchObjects(RecursionType.Full)
                .Where(x => IsProjectItem(x.Properties.RootItem));
        }

        public IEnumerable<ItemIdentifier> GetRelBranches(string branch)
        {
            var branchObj = GetBranchObject(branch);

            var childBranches = VersionControlServer.QueryBranchObjects(new ItemIdentifier(branch), RecursionType.OneLevel)
                .Where(x => IsProjectItem(x.Properties.RootItem) && x.Properties.RootItem.Item != branch);

            if (branchObj != null)
                yield return branchObj.Properties.ParentBranch;

            foreach (var child in childBranches)
                yield return child.Properties.RootItem;

            //return versionControlServer.QueryMergeRelationships(branch)
            //    .Where(x => IsProjectItem(x.Item));
        }

        public IEnumerable<BranchObject> GetBranchHierarchy(string branch)
        {
            var firstBranch = GetFirstParentBranch(branch);

            return VersionControlServer.QueryBranchObjects(new ItemIdentifier(firstBranch), RecursionType.Full)
                .Where(x => IsProjectItem(x.Properties.RootItem));               
        }

        public IEnumerable<BranchObject> GetBranchHierarchy(int changesetId)
        {
            var branch = GetBranch(changesetId);

            if (string.IsNullOrEmpty(branch))
                return null;

            return GetBranchHierarchy(branch);
        }

        private string GetFirstParentBranch(string branch)
        {
            BranchObject branchObj = GetBranchObject(branch);

            while (branchObj != null)
            {
                if (branchObj.Properties.ParentBranch != null)
                {
                    branch = branchObj.Properties.ParentBranch.Item;
                    branchObj = GetBranchObject(branch);
                }
                else
                    branchObj = null;
            }

            return branch;
        }

        private BranchObject GetBranchObject(string branch)
        {
            return VersionControlServer.QueryBranchObjects(new ItemIdentifier(branch), RecursionType.None)
                    .FirstOrDefault(x => IsProjectItem(x.Properties.ParentBranch));
        }

        private bool IsProjectItem(ItemIdentifier item)
        {
            return item != null && item.Item.StartsWith("$/" + _projectName);
        }

        public string GetBranch(int changesetId)
        {
            var item = VersionControlServer.QueryBranchObjectOwnership(new[] { changesetId })
                .FirstOrDefault(x => IsProjectItem(x.RootItem));

            if (item == null)
                return null;

            return item.RootItem.Item;
        }

        public IEnumerable<ExtendedMerge> TrackMerges(int changesetId)
        {
            var branch = GetBranch(changesetId);

            if (string.IsNullOrEmpty(branch))
                return new List<ExtendedMerge>();

            var branches = GetBranchHierarchy(branch)
                .Select(x => x.Properties.RootItem).ToArray();

            if (!branches.Any())
                branches = new[] { new ItemIdentifier(branch) };

            return VersionControlServer.TrackMerges(new[] { changesetId },
                new ItemIdentifier(branch),
                branches.ToArray(),
                null);
        }

        public void AutoMergeResolveConflict(Conflict conflict)
        {
            if (Workspace.MergeContent(conflict, false))
            {
                // if TotalConflicting > 0, automerge impossible
                if (conflict.ContentMergeSummary.TotalConflicting == 0)
                {
                    conflict.Resolution = Resolution.AcceptMerge;
                    Workspace.ResolveConflict(conflict);
                }
            }
        }

        public void TakeSourceResolveConflict(Conflict conflict)
        {
            conflict.Resolution = Resolution.AcceptTheirs;
            Workspace.ResolveConflict(conflict);
        }
    }
}
